﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JXT.PrimaryKey.Batman.Extensions;
using JXT.PrimaryKey.Batman.Validation;
using JXT.PrimaryKey.Batman.Collections;
using JXT.PrimaryKey.Batman.Core.Extensions;
using JXT.PrimaryKey.Batman.Domain.Models;
using JXT.PrimaryKey.Batman.Domain.Services;
using JXT.PrimaryKey.Batman.Domain.Repositories;
using JXT.PrimaryKey.Batman.Domain.Specifications;
using JXT.PrimaryKey.Batman.Domain.UserSystem.Models;
using JXT.PrimaryKey.Batman.Domain.UserSystem.Repositories;
using Security = JXT.PrimaryKey.Batman.Core.Security;

namespace JXT.PrimaryKey.Batman.Domain.UserSystem.Services.Default
{
    public class UserService : CachedServiceBase<User>, IUserService
    {
        public User Register(string name, string mail, string password, string realName, Gender gender, string identity, string registerIp, IEnumerable<Role> roles)
        {
            User user = new User()
            {
                Name = name,
                Mail = mail,
                Password = password
            };
            identity = identity ?? String.Empty;
            realName = realName ?? String.Empty;
            if (String.IsNullOrEmpty(registerIp))
            {
                registerIp = "127.0.0.1";
            }
            //检查格式
            var result = Validator.Validate(user);
            if (!result.IsValid)
            {
                throw new ValidationException("entity validate error!", result.Errors);
            }
            //检查是否重复
            var userCheckSpecification = SpecificationFactory.CreateSpecification<User>();
            userCheckSpecification.And(e => e.Name == name);
            userCheckSpecification.Or(e => e.Mail == mail);
            if (Exists(userCheckSpecification))
            {
                throw new ObjectExistsException("user");
            }
            RepositoryContext.BeginTransaction();
            try
            {
                LoadRepository<IUserRepository>().Add(user);
                UserDetail detail = new UserDetail()
                {
                    UserId = user.Id,
                    Address = String.Empty,
                    Mobile = String.Empty,
                    NickName = String.Empty,
                    Phone = String.Empty,
                    RealName = realName,
                    Signature = String.Empty,
                    Source = 0,
                    Gender = gender,
                    Identity = identity
                };
                UserStatus status = new UserStatus()
                {
                    UserId = user.Id,
                    LoginNum = 0,
                    LastLoginIp = registerIp,
                    LastLoginTime = DateTime.Now,
                    RegisterIp = registerIp,
                    RegisterTime = DateTime.Now,
                    LoginToken = String.Empty
                };
                user.Detail = detail;
                user.Status = status;
                LoadRepository<IUserRepository>().AddDetail(detail);
                LoadRepository<IUserRepository>().AddStatus(status);
                if (roles != null && roles.Count() > 0)
                {
                    SetRoles(user, roles);
                }
                else
                {
                    SetRoles(user, new[] { new Role() { Id = Role.UserRoleId } });
                }
                RepositoryContext.CommitTransaction();
                return user;
            }
            catch
            {
                RepositoryContext.RollbackTransaction();
                throw;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public void Save(User entity)
        {
            if (entity.Id <= 0)
                throw new InvalidOperationException("please call Register method!");
            //检查格式
            var result = Validator.Validate(entity);
            if (!result.Equals(ValidationResult.Success))
            {
                throw new ValidationException("entity validate error!", result.Errors);
            }
            var find = LoadRepository<IUserRepository>().GetSingle(entity.Id);
            if (find == null)
                throw new ObjectNotFoundException("user");
            RepositoryContext.BeginTransaction();
            try
            {
                find.Mail = entity.Mail;
                find.Password = entity.Password;
                LoadRepository<IUserRepository>().Update(entity);
                if (entity.Detail != null && entity.Detail.UserId == find.Id)
                {
                    LoadRepository<IUserRepository>().UpdateDetail(entity.Detail);
                }
                if (entity.Status != null && entity.Detail.UserId == find.Id)
                {
                    LoadRepository<IUserRepository>().UpdateStatus(entity.Status);
                }
                RemoveCachedSingle(find.Id);
                RepositoryContext.CommitTransaction();
            }
            catch
            {
                RepositoryContext.RollbackTransaction();
                throw;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public void Delete(User entity)
        {
            if (entity.Id <= 0)
                throw new InvalidOperationException("key value is not support!");
            RepositoryContext.BeginTransaction();
            try
            {
                LoadRepository<IUserRepository>().DeleteDetail(entity.Id);
                LoadRepository<IUserRepository>().DeleteStatus(entity.Id);
                LoadRepository<IUserRepository>().Delete(entity.Id);
                RemoveCachedSingle(entity.Id);
                RepositoryContext.CommitTransaction();
            }
            catch
            {
                RepositoryContext.RollbackTransaction();
                throw;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public bool Exists(ISpecification<User> specification)
        {
            return LoadRepository<IUserRepository>().Count(specification) > 0;
        }


        public bool ExistsName(string username)
        {
            var userCheckSpecification = SpecificationFactory.CreateSpecification<User>();
            userCheckSpecification.And(e => e.Name == username);
            return LoadService<IUserService>().Exists(userCheckSpecification);
        }

        public bool ExistsMail(string mail)
        {
            var userCheckSpecification = SpecificationFactory.CreateSpecification<User>();
            userCheckSpecification.And(e => e.Mail == mail);
            return LoadService<IUserService>().Exists(userCheckSpecification);
        }

        public int Count(ISpecification<User> specification)
        {
            return LoadRepository<IUserRepository>().Count(specification);
        }

        public User GetSingle(ISpecification<User> specification)
        {
            var find = LoadRepository<IUserRepository>().GetSingle(specification);
            //填充附加属性和扩展属性
            FillSingle(find);
            return find;
        }

        public User GetSingle(int userId)
        {
            var specification = SpecificationFactory.CreateSpecification<User>();
            specification.And(e => e.Id == userId);
            return GetSingle(specification);
        }

        public User GetCachedSingle(int id)
        {
            string key = String.Format("{0}_{1}", CacheSpace, id);
            User result = Cache.Get(key) as User;
            if (result == null)
            {
                var specification = SpecificationFactory.CreateSpecification<User>();
                specification.And(e => e.Id == id);
                result = GetSingle(specification);
                result.Detail = GetDetail(result);
                result.Status = GetStatus(result);
                Cache.Add(key, result, TimeSpan.FromDays(1)); //默认缓存一天
            }
            return result;
        }

        public void RemoveCachedSingle(int id)
        {
            string key = String.Format("{0}_{1}", CacheSpace, id);
            Cache.Remove(key);
        }

        public IPagedCollection<User> GetList(IPagedSpecification<User> specification)
        {
            var list = LoadRepository<IUserRepository>().GetList(specification);
            return list;
        }

        public UserDetail GetDetail(User user)
        {
            if (user.Id <= 0)
                throw new InvalidOperationException("key value is not support!");
            return LoadRepository<IUserRepository>().GetDetail(user.Id);
        }

        public UserStatus GetStatus(User user)
        {
            if (user.Id <= 0)
                throw new InvalidOperationException("key value is not support!");
            return LoadRepository<IUserRepository>().GetStatus(user.Id);
        }

        public void SetAuthority(User user, Authority[] authorityArray)
        {
            if (authorityArray == null)
                return;
            //验证用户
            if (user.Id <= 0)
                throw new InvalidOperationException("user key value is not support!");
            //验证权限对象
            var validator = LoadValidator<Authority>();
            foreach (var item in authorityArray)
            {
                var validationResult = validator.Validate(item);
                if (!validationResult.IsValid)
                    throw new ValidationException("some authorities validation error!", validationResult.Errors);
            }
            LoadRepository<IUserRepository>().SetAuthority(user.Id, authorityArray);
            RemoveCachedSingle(user.Id);
        }

        public void SetRoles(User user, IEnumerable<Role> roles)
        {
            if (roles == null)
                return;
            //验证用户
            if (user.Id <= 0)
                throw new InvalidOperationException("user key value is not support!");
            int[] currentRoleIdArray = LoadRepository<IUserRepository>().GetRoleIdArray(user.Id);
            List<int> addIdList = new List<int>();
            List<int> deleteIdList = new List<int>(currentRoleIdArray);
            foreach (var item in roles)
            {
                if (item.Id <= 0)
                    continue;
                if (deleteIdList.Any(e => e == item.Id))
                {
                    deleteIdList.Remove(item.Id);
                }
                else
                {
                    addIdList.Add(item.Id);
                }
            }
            RepositoryContext.BeginTransaction();
            try
            {
                LoadRepository<IUserRepository>().AddRoles(user.Id, addIdList.ToArray());
                LoadRepository<IUserRepository>().DeleteRoles(user.Id, deleteIdList.ToArray());
                RepositoryContext.CommitTransaction();
            }
            catch
            {
                RepositoryContext.RollbackTransaction();
                throw;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public IEnumerable<Role> GetRoles(User user)
        {
            //验证用户
            if (user.Id <= 0)
                throw new InvalidOperationException("user key value is not support!");
            var roleIdArray = LoadRepository<IUserRepository>().GetRoleIdArray(user.Id);
            return LoadService<IRoleService>().GetList().Where(e => roleIdArray.Any(r => r == e.Id)).ToList();
        }

        public void UpdateStatus(User entity, string loginIp, string token = null)
        {
            if (entity == null || entity.Id <= 0)
                throw new InvalidOperationException("user is null or key value is not support!");
            var status = GetStatus(entity);
            status.LastLoginIp = loginIp;
            status.LoginNum++;
            status.LastLoginTime = DateTime.Now;
            if (String.IsNullOrWhiteSpace(token))
            {
                status.LoginToken = Guid.NewGuid().ToGuidString();
            }
            else
            {
                status.LoginToken = token;
            }
            LoadRepository<IUserRepository>().UpdateStatus(status);
            RemoveCachedSingle(entity.Id);
        }

        public void UpdateDetail(User user, UserDetail detail)
        {
            if (user == null || detail == null)
                throw new InvalidOperationException("user and detail are null or key value is not support!");
            detail.UserId = user.Id;
            LoadRepository<IUserRepository>().UpdateDetail(detail);
            RemoveCachedSingle(user.Id);
        }

        public User GetLoginSingle(string token)
        {
            if (String.IsNullOrWhiteSpace(token))
                throw new ArgumentNullException("token");
            LoginStatus status = Cache.Get(token) as LoginStatus;
            if (status == null)
            {
                status = LoadRepository<IUserRepository>().GetLoginStatus(token);
                if (status == null)
                    return null;
                if (DateTime.Now > status.Expires)
                {
                    LoadRepository<IUserRepository>().DeleteLoginStatus(status.Token);
                    return null;
                }
                Cache.Add(status.Token, status, status.Expires.ToCurrentTimeSpan());
            }
            if (DateTime.Now > status.Expires)
            {
                LoadRepository<IUserRepository>().DeleteLoginStatus(status.Token);
                return null;
            }
            var user = GetCachedSingle(status.UserId);
            user.loginStatus = status;
            user.Roles = GetRoles(user);
            return user;
        }

        public LoginStatus Login(User user, string loginip, DateTime? expires = null, int? oauthId = null, string oauthToken = null, string oauthTokenSecret = null, string oauthUserId = null, string oauthVerifier = null)
        {
            if (user == null)
            {
                LoadService<IAuthenticationLogService>().Add(new AuthenticationLog() { Title = "登录错误", Content = "没有该用户" });
                return null;
            }
            var realAuthorityList = user.MergeAuthorities();
            if (Security.SecurityExtensions.Check(realAuthorityList, "islock", true))
            {
                LoadService<IAuthenticationLogService>().Add(new AuthenticationLog() { Title = "权限错误", Content = "该用户已经被锁定,不允许登录" });
                throw new Security.SecurityException("user is locked!");
            }
            RepositoryContext.BeginTransaction();
            try
            {
                //开始往loginstatus表中添加数据，更新用户状态表，写缓存
                LoginStatus loginStatus = new LoginStatus();
                loginStatus.UserId = user.Id;
                loginStatus.LoginTime = DateTime.Now;
                loginStatus.LoginIp = loginip;
                loginStatus.LastActiveIp = loginip;
                loginStatus.LastActiveTime = DateTime.Now;
                loginStatus.OAuthId = oauthId == null ? 0 : oauthId.Value;
                loginStatus.OAuthToken = String.IsNullOrWhiteSpace(oauthToken) ? String.Empty : oauthToken;
                loginStatus.OAuthTokenSecret = String.IsNullOrWhiteSpace(oauthTokenSecret) ? String.Empty : oauthTokenSecret;
                loginStatus.OAuthUserId = String.IsNullOrWhiteSpace(oauthUserId) ? String.Empty : oauthUserId;
                loginStatus.OAuthVerifier = String.IsNullOrWhiteSpace(oauthVerifier) ? String.Empty : oauthVerifier;
                if (expires != null)
                    loginStatus.Expires = expires.Value;
                loginStatus.Token = Guid.NewGuid().ToGuidString();
                LoadRepository<IUserRepository>().AddLoginStatus(loginStatus);
                UpdateStatus(user, loginip, loginStatus.Token);
                user.Status = GetStatus(user);
                user.Detail = GetDetail(user);
                user.loginStatus = loginStatus;
                Cache.Add(loginStatus.Token, loginStatus, loginStatus.Expires - DateTime.Now);
                RepositoryContext.CommitTransaction();
                return loginStatus;
            }
            catch (Exception ex)
            {
                RepositoryContext.RollbackTransaction();
                throw ex;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public LoginStatus Login(string name, string mail, string password, string loginip, DateTime? expires = null)
        {
            if (String.IsNullOrWhiteSpace(password))
                throw new ArgumentNullException("password");
            if (String.IsNullOrWhiteSpace(loginip))
                throw new ArgumentNullException("loginip");
            expires = expires ?? DateTime.Now.AddDays(1);
            var userSpecification = SpecificationFactory.CreateSpecification<User>();
            if (!string.IsNullOrWhiteSpace(name))
                userSpecification.And(e => e.Name == name);
            if (!string.IsNullOrWhiteSpace(mail))
                userSpecification.And(e => e.Mail == mail);
            userSpecification.And(e => e.Password == password.ToMd5());
            User user = GetSingle(userSpecification);
            return Login(user, loginip, expires);
        }

        public LoginStatus Login(OAuthUserBinding binding, string oauthToken, string oauthTokenSecret, string oauthUserId, string oauthVerifier, string loginIp, DateTime? expires = null)
        {
            if (string.IsNullOrEmpty(oauthToken) || string.IsNullOrEmpty(oauthTokenSecret))
                throw new ArgumentNullException("micorotoke or microtokesecret is not null");
            User user = LoadRepository<IUserRepository>().GetSingle(binding.UserId);
            return Login(user, loginIp, expires, binding.OAuthId, oauthToken, oauthTokenSecret, oauthUserId, oauthVerifier);
        }

        public void Logout(string token, string sysId = null)
        {
            if (string.IsNullOrWhiteSpace(token))
                return;
            User loginUser = GetLoginSingle(token);
            //if (loginUser == null || token != loginUser.Status.LoginToken)
            //    throw new Security.SecurityException("error for login status!");
            //开始删除缓存，更新userstatus表，删除loginstatus表
            RepositoryContext.BeginTransaction();
            try
            {
                Cache.Remove(token);
                LoadRepository<IUserRepository>().DeleteLoginStatus(token);
                UpdateStatus(loginUser, loginUser.Status.LastLoginIp, String.Empty);
                if (!String.IsNullOrWhiteSpace(sysId))
                {
                    var specification = SpecificationFactory.CreateSpecification<SystemUser>();
                    specification.And(e => e.SysId == sysId);
                    var sysUser = LoadRepository<ISystemUserRepository>().GetSingle(specification);
                    if (sysUser != null && !String.IsNullOrWhiteSpace(sysUser.LogoutUrl) && Uri.IsWellFormedUriString(sysUser.LogoutUrl, UriKind.Absolute))
                    {
                        LoadService<ICallTaskService>().Add(new Uri(sysUser.LogoutUrl), String.Format("log out call from {0}", sysUser.SysId));
                    }
                }
                RepositoryContext.CommitTransaction();
            }
            catch (Exception ex)
            {
                RepositoryContext.RollbackTransaction();
                throw ex;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        #region override methods

        public override RepositoryContext RepositoryContext
        {
            get
            {
                return LoadRepository<IUserRepository>().Context;
            }
        }

        protected override void FillSingle(User entity)
        {
            //base.FillSingle(entity);
            if (entity != null)
            {
                LoadService<IAuthorityTemplateService>().FillAuthorityEnumerable(entity.Authorities);
                entity.Roles = new LazyList<Role>(() => GetRoles(entity).ToList());
            }
        }

        #endregion

        public void AddOAuthUserBinding(OAuthUserBinding microuser)
        {
            LoadRepository<IUserRepository>().AddOAuthUserBinding(microuser);
        }

        public OAuthUserBinding GetOAuthUserBindingSingle(string userIdentity, int oAuthId)
        {
            return LoadRepository<IUserRepository>().GetOAuthUserBindingSingle(userIdentity, oAuthId);
        }

        public OAuthUserBinding GetOAuthUserBindingSingle(ISpecification<OAuthUserBinding> specification)
        {
            if (specification == null)
                throw new ArgumentNullException("specification");
            var result = LoadRepository<IUserRepository>().GetOAuthUserBindingSingle(specification);
            return result;
        }

        public void DeleteOAuthUserBinding(OAuthUserBinding binding)
        {
            if (binding == null)
                throw new ArgumentNullException("binding");
            LoadRepository<IUserRepository>().DeleteOAuthUserBinding(binding.Id);
        }

        public IEnumerable<OAuthUserBinding> GetOAuthUserBindingList(ISpecification<OAuthUserBinding> specification)
        {
            if (specification == null)
                throw new ArgumentNullException("specification");
            return LoadRepository<IUserRepository>().GetOAuthUserBindingList(specification);
        }

        public void RegisterAndBindingToOAuth(string username, string mail, string password, OAuthUserBinding binding)
        {
            RepositoryContext.BeginTransaction();
            try
            {
                if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(mail) || string.IsNullOrEmpty(password))
                    return;
                var entity = LoadRepository<IUserRepository>().GetOAuthUserBindingSingle(binding.OAuthUserIdentity, binding.OAuthId);
                if (entity == null)
                {
                    var registUser = LoadService<IUserService>().Register(username, mail, password.ToMd5(), null, Gender.Unknown, null, null, null);
                    binding.UserId = registUser.Id;
                    LoadService<IUserService>().AddOAuthUserBinding(binding);
                    RepositoryContext.CommitTransaction();
                    var key = buildFirstOAuthPasswordKey(registUser.Id);
                    Cache.Add(key, password);
                }
            }
            catch
            {
                RepositoryContext.RollbackTransaction();
                throw;
            }
            finally
            {
                RepositoryContext.DisposeTransaction();
            }
        }

        public string GetDefaultOAuthPassword(User entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            string key = buildFirstOAuthPasswordKey(entity.Id);
            var password = Cache.Get(key);
            if (password != null)
                Cache.Remove(key);
            return Convert.ToString(password);
        }

        public void BindingToOAuth(string username, string mail, string password, OAuthUserBinding binding)
        {
            var userSpecification = SpecificationFactory.CreateSpecification<User>();
            if (!string.IsNullOrWhiteSpace(username))
                userSpecification.And(e => e.Name == username);
            if (!string.IsNullOrWhiteSpace(mail))
                userSpecification.And(e => e.Mail == mail);
            userSpecification.And(e => e.Password == password.ToMd5());
            User user = GetSingle(userSpecification);
            if (user == null)
            {
                LoadService<IAuthenticationLogService>().Add(new AuthenticationLog() { Title = "登录错误", Content = "没有该用户" });
                return;
            }
            else
            {
                binding.UserId = user.Id;
                LoadRepository<IUserRepository>().AddOAuthUserBinding(binding);
            }
        }

        private string buildFirstOAuthPasswordKey(int userId)
        {
            return String.Format("oauth_first_password_{0}", userId);
        }
    }
}
